package com.talos.framework.common.util.collection;

import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;

public final class ListUtils {

    private ListUtils() {

    }

    public static <T> List<T> of(T t) {
        List<T> list = new ArrayList<>();

        list.add(t);

        return list;
    }

    @SafeVarargs
    public static <T> List<T> of(T... ts) {
        return Arrays.asList(ts);
    }

    public static <T> T first(Collection<T> collection) {
        if (EmptyUtils.isEmpty(collection)) {
            return null;
        }

        return collection.stream().findFirst().orElse(null);
    }

    public static <T> T first(Collection<T> collection, Predicate<T> predicate) {
        if (EmptyUtils.isEmpty(collection)) {
            return null;
        }

        for (T t : collection) {
            if (predicate.test(t)) {
                return t;
            }
        }

        return null;
    }

    public static <T> T last(Collection<T> collection) {
        if (EmptyUtils.isEmpty(collection)) {
            return null;
        }

        return collection.stream().reduce((a, b) -> b).orElse(null);
    }

    public static <T, R> String join(Collection<T> collection, Function<? super T, ? extends R> key, String separator) {
        return ListUtils.join(ListUtils.toSet(collection, key), separator);
    }

    public static String join(Collection<?> collection, String separator) {
        if (EmptyUtils.isEmpty(collection)) {
            return "";
        }

        Iterator<?> iterator = collection.iterator();

        StringBuilder sb = new StringBuilder();

        while (iterator.hasNext()) {
            Object value = iterator.next();

            if (value != null) {
                sb.append(value);
            }

            if (iterator.hasNext()) {
                sb.append(separator);
            }
        }

        return sb.toString();
    }

    public static List<String> split(String value, String regex) {
        return ListUtils.split(value, regex, 0);
    }

    public static List<String> split(String value, String regex, int limit) {
        if (EmptyUtils.isEmpty(value)) {
            return EmptyUtils.emptyList();
        }

        return Arrays.asList(value.split(regex, limit));
    }

    public static LinkedHashMap<String, Object> sort(Map<String, ?> map) {
        List<String> keys = new ArrayList<>(map.keySet());

        Collections.sort(keys);

        LinkedHashMap<String, Object> sortedMap = new LinkedHashMap<>();

        for (String k : keys) {
            Object v = map.get(k);

            if (v == null) {
                continue;
            }

            sortedMap.put(k, v);
        }

        return sortedMap;
    }

    public static <T, C extends Comparable<? super C>> List<T> sort(Collection<T> collection, Function<? super T, ? extends C> comparator) {
        return ListUtils.sort(collection, Comparator.comparing(comparator));
    }

    public static <T, C extends Comparable<? super C>> List<T> sortReversed(Collection<T> collection, Function<? super T, ? extends C> comparator) {
        return ListUtils.sort(collection, Comparator.comparing(comparator).reversed());
    }

    public static <T> List<T> sort(Collection<T> collection) {
        if (EmptyUtils.isEmpty(collection)) {
            return EmptyUtils.emptyList();
        }

        return collection.stream().sorted().collect(Collectors.toList());
    }

    public static <T> List<T> sort(Collection<T> collection, Comparator<? super T> comparator) {
        if (EmptyUtils.isEmpty(collection)) {
            return EmptyUtils.emptyList();
        }

        return collection.stream().sorted(comparator).collect(Collectors.toList());
    }

    public static <T> boolean anyMatch(Collection<T> collection, Predicate<? super T> predicate) {
        if (EmptyUtils.isEmpty(collection)) {
            return false;
        }

        for (T t : collection) {
            if (predicate.test(t)) {
                return true;
            }
        }

        return false;
    }

    public static <T> boolean allMatch(Collection<T> collection, Predicate<? super T> predicate) {
        if (EmptyUtils.isEmpty(collection)) {
            return false;
        }

        for (T t : collection) {
            if (!predicate.test(t)) {
                return false;
            }
        }

        return true;
    }

    public static <T> boolean allMatch(Collection<T> collection, BiPredicate<? super T, ? super T> predicate) {
        if (EmptyUtils.isEmpty(collection)) {
            return false;
        }

        T a = ListUtils.first(collection);

        for (T b : collection) {
            if (!predicate.test(a, b)) {
                return false;
            }
        }

        return true;
    }

    public static <T> void foreach(Collection<T> collection, Consumer<? super T> consumer) {
        if (EmptyUtils.isEmpty(collection)) {
            return;
        }

        for (T t : collection) {
            consumer.accept(t);
        }
    }

    public static <T> List<T> filter(Collection<T> collection, Predicate<? super T> predicate) {
        if (EmptyUtils.isEmpty(collection)) {
            return EmptyUtils.emptyList();
        }

        List<T> list = new ArrayList<>(collection.size());

        for (T t : collection) {
            if (predicate.test(t)) {
                list.add(t);
            }
        }

        return list;
    }

    public static <T, R> Set<R> toSet(Collection<T> collection, Function<? super T, ? extends R> key) {
        if (EmptyUtils.isEmpty(collection)) {
            return EmptyUtils.emptySet();
        }

        Set<R> set = new HashSet<>(collection.size());

        for (T t : collection) {
            R r = key.apply(t);

            if (r == null) {
                continue;
            }

            set.add(r);
        }

        return set;
    }

    public static <T, R> List<R> toList(Collection<T> collection, Function<? super T, ? extends R> key) {
        if (EmptyUtils.isEmpty(collection)) {
            return EmptyUtils.emptyList();
        }

        List<R> list = new ArrayList<>(collection.size());

        for (T t : collection) {
            R r = key.apply(t);

            if (r == null) {
                continue;
            }

            list.add(r);
        }

        return list;
    }

    public static <K, V, R> List<R> toList(Map<K, V> map, Function<V, R> mapper) {
        if (EmptyUtils.isEmpty(map)) {
            return EmptyUtils.emptyList();
        }

        List<R> list = new ArrayList<>(map.size());

        for (Map.Entry<K, V> entry : map.entrySet()) {
            R r = mapper.apply(entry.getValue());

            if (r == null) {
                continue;
            }

            list.add(r);
        }

        return list;
    }

    public static <K, V, R> List<R> toList(Map<K, V> map, BiFunction<V, K, R> mapper) {
        if (EmptyUtils.isEmpty(map)) {
            return EmptyUtils.emptyList();
        }

        List<R> list = new ArrayList<>(map.size());

        for (Map.Entry<K, V> entry : map.entrySet()) {
            R r = mapper.apply(entry.getValue(), entry.getKey());

            if (r == null) {
                continue;
            }

            list.add(r);
        }

        return list;
    }

    public static <T, K> Map<K, T> toMap(Collection<T> collection, Function<T, K> key) {
        return ListUtils.toMap(collection, key, Function.identity());
    }

    public static <T, K, V> Map<K, V> toMap(Collection<T> collection, Function<T, K> key, Function<T, V> value) {
        if (EmptyUtils.isEmpty(collection)) {
            return EmptyUtils.emptyMap();
        }

        Map<K, V> map = new HashMap<>(collection.size());

        for (T t : collection) {
            K k = key.apply(t);

            if (k == null) {
                continue;
            }

            V v = value.apply(t);

            if (v == null) {
                continue;
            }

            map.put(k, v);
        }

        return map;
    }

    public static <T, K> Map<K, List<T>> toMapList(Collection<T> collection, Function<T, K> key) {
        return ListUtils.toMapList(collection, key, Function.identity());
    }

    public static <T, K, V> Map<K, List<V>> toMapList(Collection<T> collection, Function<T, K> key, Function<T, V> value) {
        if (EmptyUtils.isEmpty(collection)) {
            return EmptyUtils.emptyMap();
        }

        Map<K, List<V>> map = new HashMap<>(collection.size());

        for (T t : collection) {
            K k = key.apply(t);

            if (k == null) {
                continue;
            }

            if (!map.containsKey(k)) {
                map.put(k, new ArrayList<>());
            }

            V v = value.apply(t);

            if (v == null) {
                continue;
            }

            map.get(k).add(v);
        }

        return map;
    }

    public static <T, R> Map<R, List<T>> groupingBy(Collection<T> collection, Function<T, R> classifier) {
        if (EmptyUtils.isEmpty(collection)) {
            return EmptyUtils.emptyMap();
        }

        return collection.stream().collect(Collectors.groupingBy(classifier));
    }

    public static <T, K, U> Map<K, List<U>> groupingBy(Collection<T> collection, Function<T, K> classifier, Function<T, U> mappingFunction) {
        if (EmptyUtils.isEmpty(collection)) {
            return EmptyUtils.emptyMap();
        }

        return collection.stream()
                .collect(Collectors.groupingBy(classifier, Collectors.mapping(mappingFunction, Collectors.toList())));
    }

    public static <T, K, R> List<R> toGroup(Collection<T> collection, Function<T, K> key, BiFunction<K, T, R> creator, BiConsumer<T, R> value) {
        if (EmptyUtils.isEmpty(collection)) {
            return EmptyUtils.emptyList();
        }

        Map<Object, R> map = new HashMap<>();

        for (T t : collection) {
            K k = key.apply(t);

            if (k == null) {
                continue;
            }

            R r = map.get(k);

            if (r == null) {
                r = creator.apply(k, t);

                map.put(k, r);
            }

            value.accept(t, r);
        }

        return new ArrayList<>(map.values());
    }

    public static <T, K, R> List<R> toTree(Collection<T> collection, Function<T, K> key, Function<T, K> parent, Function<R, Collection<R>> children, Function<T, R> mapper) {
        List<R> parents = new ArrayList<>();

        Map<K, R> parentMap = ListUtils.toMap(collection, key, item -> {
            R r = mapper.apply(item);

            if (!EmptyUtils.hasValue(parent.apply(item))) {
                parents.add(r);
            }

            return r;
        });

        for (T t : collection) {
            K k = parent.apply(t);

            if (!EmptyUtils.hasValue(k)) {
                continue;
            }

            R r = parentMap.get(k);

            if (r == null) {
                continue;
            }

            children.apply(r).add(parentMap.get(key.apply(t)));
        }

        return parents;
    }

}
